package gates;

import java.awt.Graphics;

import master.*;


/**
 * A full Ripple Carry adder algorithm/circuit that works as a Gate and can be applied to a quantum
 * Register. It requires the register qBit size to be a multiple of 3 and the first third has to be 
 * set to zero for the adder to work reversibly. It uses basic gates for all operations and simply 
 * deals with applying the gates to the appropriate qBits.
 * 
 * @author Oliver Wiese
 * 
 */
public class RippleCarryAdder extends QuantumGate{


	QuantumGate swap, tGate, cNGate;
	QuantumAdder adder;
	FullQuantumAdder fadder;
	

	public RippleCarryAdder(){

	}


	/**
	 * act on the register with the Algorithm
	 * 
	 * @param register
	 */
	public void actOnReg(QuantumRegister register) {

		//preliminary register checks
		checkRegisterSize(register);
		checkCarryBits(register);
		
		
		// find the third of the register
		int thirdQBits = register.getQBitLength()/3;
		int carryOut = 2*thirdQBits;  // carry out bits start here
		
		
		int b1 = 0;
		int a1 = thirdQBits;
		
		//apply first adder
		adder = new QuantumAdder(a1, b1, carryOut);
		adder.actOnReg(register);
		
		//apply swap gate
		swap = new SwapGate(a1,carryOut);
		swap.actOnReg(register);


		//start loop for the remaining qBits 
		for(int c1=1;c1<thirdQBits;c1++){
			
			int c2 = c1 + thirdQBits; // second qBit to act on is at the first qBit plus a third of the register
			
			carryOut = c2 + thirdQBits; // carryOut qBit is at a further third
		
			// apply full Adder
			fadder = new FullQuantumAdder(c2-1,c2,c1,carryOut);
			fadder.actOnReg(register);
		
			// apply swap gate
			swap = new SwapGate(c2,carryOut);
			swap.actOnReg(register);
			
		}
		// apply final swap for formatting
		swap = new SwapGate(register.getQBitLength()-1, register.getQBitLength()-thirdQBits-1);
		swap.actOnReg(register);
	}

	/**
	 * allows to apply the algorithm on reverse on a register
	 * same process as with the normal act on register only it applies all gates backwards
	 * @param register
	 */
	public void actOnRegReverse(QuantumRegister register){
		checkRegisterSize(register);
		int thirdQBits = register.getQBitLength()/3;


		int lastqbit = register.getQBitLength()-1;
		swap = new SwapGate(lastqbit, lastqbit-thirdQBits);
		swap.actOnReg(register);
		
		int carryOut = 3*thirdQBits;
		
		for(int c1=thirdQBits-1;c1>=1;c1--){
			
			int c2 = c1 + thirdQBits;
			
			carryOut = c2 + thirdQBits;
			
			swap = new SwapGate(c2,carryOut);
			swap.actOnReg(register);
			
			fadder = new FullQuantumAdder(c2-1,c2,c1,carryOut);
			fadder.actOnRegReverse(register);
		}
		carryOut--;
		int b1 = 0;
		int a1 = thirdQBits;

		swap = new SwapGate(a1,carryOut);
		swap.actOnReg(register);
		
		adder = new QuantumAdder(a1, b1, carryOut);
		adder.actOnRegReverse(register);
		}


	/**
	 * Checks whether the register fulfils the size requirements for the Adder
	 * @param register
	 */
	public void checkRegisterSize(QuantumRegister register) {
		if(register.getQBitLength()%3 != 0){
			throw new IllegalArgumentException("the register is not an appropriat size, it must be be a triple of the basic register part");
		}
	}
	
	/**
	 * checks whether the given Register has all its Carry bits set to zero
	 * if not then it throws and exception
	 * @param register
	 */
	public void checkCarryBits(QuantumRegister register){
		int part = register.getQBitLength()/3;
		int addregister = power2(register.getQBitLength()-part);
		for (int n =addregister; n<register.getDimension();n++){
			if (register.getCoefficient(n).magnitudeFloat()!=0.0f){
				throw new IllegalArgumentException("the first third of the bits is not zero");
			}
		}
	}


	/**
	 * used for visual representation of the gate in the UI
	 */

	
	/**
	 * shifts an integer left by one
	 * @param n
	 * @return 2^n
	 */
	public int power2(int n){
		return 1<<n;
	}


	/**
	 * prints out the register as states with coefficients
	 * used only for testing
	 * @param reg
	 */
	public static void printOut(QuantumRegister reg){
		for (int n=0;n<reg.getDimension();n++){
			if(reg.getCoefficient(n).magnitudeFloat()>0)
				System.out.println(" " + reg.getCoefficient(n) + "|" + Integer.toBinaryString(n) + "("+ n +")>");
		}
		System.out.println("");
	}
		
	/**
	 * Returns null as all bits are acted on
	 */
	public int[] getActingBits() {
		return null;
	}
	
	public static void main(String[] args){
		QuantumRegister reg = new QuantumRegister(9);
		RippleCarryAdder adderGate = new RippleCarryAdder();


		reg.setToEigenstate(43);
		printOut(reg);

		/*
		for (int j = 0; j < reg.getQBitLength()-3; j++){
			Hadamard H = new Hadamard(j);
			H.actOnReg(reg);
		}
		printOut(reg); //*/

		adderGate.actOnReg(reg);
		printOut(reg);
		
		adderGate.actOnRegReverse(reg);
		printOut(reg);
		/*
		for (int j = 0; j < reg.getQBitLength()-3; j++){
			Hadamard H = new Hadamard(j);
			H.actOnReg(reg);
		}
		printOut(reg);//*/
	}


}
